package goinsta

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"strconv"
	"time"
)

// UploadVideo post image from io.Reader to instagram.
func (insta *Instagram) UploadVideo(param *VideoParam, photoCaption string) (Item, error) {
	out := Item{}

	result, err := insta.uploadVideo(param.Video)
	if err != nil {
		log.Println("[validate video]", err.Error())
		return out, err
	}

	err = insta.uploadSegmentVideo(param, result)
	if err != nil {
		log.Println("[upload video clip]", err.Error())
		return out, err
	}
	// 上传完视频需要等待解码,否则会报解码未完成错误.
	// 延迟时间根据文件大小而定
	time.Sleep(time.Second)
	err = insta.uploadThumbnail(param, photoCaption, result.UploadID)
	if err != nil {
		log.Println("[upload thumbnail]", err.Error())
		return out, err
	}

	config := map[string]interface{}{
		"upload_id":          result.UploadID,
		"source_type":        3,
		"poster_frame_index": 0,
		"length":             0.00,
		"audio_muted":        false,
		"filter_type":        0,
		"video_result":       "deprecated",
		"caption":            photoCaption,
		"device":             goInstaDeviceSettings,
		"clips": map[string]interface{}{
			"length":          param.Duration,
			"source_type":     "3",
			"camera_position": "back",
		},
		"extra": map[string]interface{}{
			"source_width":  param.Width,
			"source_height": param.Height,
		},
	}

	data, err := insta.prepareData(config)
	if err != nil {
		return out, err
	}

	body, err := insta.sendRequest(&reqOptions{
		Endpoint: "media/configure/?video=1",
		Query:    generateSignature(data),
		IsPost:   true,
	})
	if err != nil {
		return out, err
	}
	var uploadResult struct {
		Media    Item   `json:"media"`
		UploadID string `json:"upload_id"`
		Status   string `json:"status"`
	}
	err = json.Unmarshal(body, &uploadResult)
	if err != nil {
		return out, err
	}

	if uploadResult.Status != "ok" {
		return out, fmt.Errorf("invalid status, result: %s", uploadResult.Status)
	}

	return uploadResult.Media, nil
}

func (insta *Instagram) uploadVideo(video io.Reader) (*videoResult, error) {

	uploadID := time.Now().Unix()

	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	w.WriteField("upload_id", strconv.FormatInt(uploadID, 10))
	w.WriteField("_uuid", insta.uuid)
	w.WriteField("_csrftoken", insta.token)
	w.WriteField("media_type", "2")

	if err := w.Close(); err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", goInstaAPIUrl+"upload/video/", &b)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-IG-Capabilities", "3Q4=")
	req.Header.Set("X-IG-Connection-Type", "WIFI")
	req.Header.Set("Host", "i.instagram.com")
	req.Header.Set("Cookie2", "$Version=1")
	req.Header.Set("Accept-Language", "en-US")
	//req.Header.Set("Accept-Encoding", "gzip, deflate")
	req.Header.Set("Content-type", w.FormDataContentType())
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("User-Agent", goInstaUserAgent)

	resp, err := insta.c.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("invalid status code, result: %s", resp.Status)
	}
	//fmt.Println("[response]", string(body))

	var result videoResult
	err = json.Unmarshal(body, &result)
	if err != nil {
		log.Println("[unmarshal]", err.Error())
		return nil, err
	}
	if result.Status != "ok" {
		return nil, fmt.Errorf("unknown error, status: %s", result.Status)
	}
	return &result, nil
}

func (insta *Instagram) uploadSegmentVideo(param *VideoParam, result *videoResult) error {

	videoData, err := ioutil.ReadAll(param.Video)
	if err != nil {
		fmt.Println(err)
	}

	// 固定未4
	length := len(result.UploadUrls)
	videoSize := param.Size
	requestSize := videoSize / int64(length)
	lastRequestExtra := videoSize - requestSize*int64(length-1)

	uploadUrl := result.UploadUrls[length-1].Url
	uploadJob := result.UploadUrls[length-1].Job
	var start, end int64
	for i := 0; i < length; i++ {
		start = int64(i) * requestSize
		if i == length-1 {
			end = start + lastRequestExtra
		} else {
			end = start + requestSize
		}
		var buf = bytes.NewBuffer(videoData[start:end])
		req, err := http.NewRequest("POST", uploadUrl, buf)
		if err != nil {
			return err
		}
		req.Header.Set("X-IG-Capabilities", "3Q4=")
		req.Header.Set("X-IG-Connection-Type", "WIFI")
		req.Header.Set("Cookie2", "$Version=1")
		req.Header.Set("Accept-Language", "en-US")
		req.Header.Set("Accept-Encoding", "gzip, deflate")
		req.Header.Set("Content-type", "application/octet-stream")
		req.Header.Set("Session-ID", result.UploadID)
		req.Header.Set("Connection", "keep-alive")
		req.Header.Set("Content-Disposition", "attachment; filename='video.mov'")
		req.Header.Set("job", uploadJob)
		req.Header.Set("Host", "upload.instagram.com")
		req.Header.Set("User-Agent", goInstaUserAgent)

		contentRange := fmt.Sprintf("bytes %d-%d/%d", start, end-1, videoSize)
		req.Header.Set("Content-Duration", strconv.FormatInt(end-start, 10))
		req.Header.Set("Content-Range", contentRange)

		resp, err := insta.c.Do(req)
		if err != nil {
			return err
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		} else {
			fmt.Println(string(body))
		}
		if !(resp.StatusCode == 200 || resp.StatusCode == 201) {
			return fmt.Errorf("invalid status code, result: %s", resp.Status)
		}
	}
	return nil
}

func (insta *Instagram) uploadThumbnail(param *VideoParam, photoCaption string, uploadID string) error {
	photoName := fmt.Sprintf("pending_media_%d.jpg", uploadID)
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	w.WriteField("upload_id", uploadID)
	w.WriteField("_uuid", insta.uuid)
	w.WriteField("_csrftoken", insta.token)
	var compression = map[string]interface{}{
		"lib_name":    "jt",
		"lib_version": "1.3.0",
		"quality":     param.Quality,
	}
	cBytes, _ := json.Marshal(compression)
	w.WriteField("image_compression", toString(cBytes))

	fw, err := w.CreateFormFile("photo", photoName)
	if err != nil {
		return err
	}

	var buf bytes.Buffer
	rdr := io.TeeReader(param.Thumbnail, &buf)
	if _, err = io.Copy(fw, rdr); err != nil {
		return err
	}
	if err := w.Close(); err != nil {
		return err
	}
	req, err := http.NewRequest("POST", goInstaAPIUrl+"upload/photo/", &b)
	if err != nil {
		return err
	}
	req.Header.Set("X-IG-Capabilities", "3Q4=")
	req.Header.Set("X-IG-Connection-Type", "WIFI")
	req.Header.Set("Cookie2", "$Version=1")
	req.Header.Set("Accept-Language", "en-US")
	req.Header.Set("Accept-Encoding", "gzip, deflate")
	req.Header.Set("Content-type", w.FormDataContentType())
	req.Header.Set("Connection", "close")
	req.Header.Set("User-Agent", goInstaUserAgent)

	resp, err := insta.c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("invalid status code, result: %s", resp.Status)
	}
	var result struct {
		UploadID       string      `json:"upload_id"`
		XsharingNonces interface{} `json:"xsharing_nonces"`
		Status         string      `json:"status"`
	}
	err = json.Unmarshal(body, &result)
	if err != nil {
		return err
	}
	if result.Status != "ok" {
		return fmt.Errorf("unknown error, status: %s", result.Status)
	}
	width, height, err := getImageDimensionFromReader(&buf)
	if err != nil {
		return err
	}
	config := map[string]interface{}{
		"media_folder": "Instagram",
		"source_type":  4,
		"caption":      photoCaption,
		"upload_id":    uploadID,
		"device":       goInstaDeviceSettings,
		"edits": map[string]interface{}{
			"crop_original_size": []int{width * 1.0, height * 1.0},
			"crop_center":        []float32{0.0, 0.0},
			"crop_zoom":          1.0,
			"filter_type":        param.FilterType,
		},
		"extra": map[string]interface{}{
			"source_width":  width,
			"source_height": height,
		},
	}

	data, err := insta.prepareData(config)
	if err != nil {
		return err
	}

	body, err = insta.sendRequest(&reqOptions{
		Endpoint: "media/configure/?",
		Query:    generateSignature(data),
		IsPost:   true,
	})
	return err
}

type videoItem struct {
	Url     string  `json:"url"`
	Job     string  `json:"job"`
	Expires float64 `json:"expires"`
}
type videoResult struct {
	UploadUrls     []videoItem `json:"video_upload_urls"`
	UploadID       string      `json:"upload_id"`
	XsharingNonces interface{} `json:"xsharing_nonces"`
	Status         string      `json:"status"`
}

type VideoParam struct {
	Video      *os.File
	Thumbnail  *os.File
	Quality    int     // 预览图质量 0
	FilterType int     // 预览图过滤 0
	Duration   float64 // 时长(s)
	Size       int64   // 文件大小(byte)
	Width      int64   // 视频分辨率宽度
	Height     int64   // 视频分辨率高度
}
